package org.squabble.service;

import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.encoding.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.squabble.dao.AccountDAO;
import org.squabble.dao.PrivilegeDAO;
import org.squabble.domain.Account;
import org.squabble.domain.AccountProperty;
import org.squabble.domain.Privilege;
import org.squabble.domain.property.AccountBooleanProperty;
import org.squabble.domain.property.AccountDateProperty;
import org.squabble.domain.property.AccountDoubleProperty;
import org.squabble.domain.property.AccountLongProperty;
import org.squabble.domain.property.AccountStringProperty;
import org.squabble.web.account.AccountDetailsCommand;
import org.squabble.web.account.AccountSettingsCommand;
import org.squabble.web.account.RegisterCommand;

@Service
public class AccountService {

	@Autowired
	private AccountDAO accountDAO;

	@Autowired
	private PrivilegeDAO privilegeDAO;

	@Autowired
	private EmailService emailService;

	@Autowired
	PasswordEncoder passwordEncoder;

	private final Log log = LogFactory.getLog(this.getClass());

	@Transactional(readOnly = true)
	public Account getAccount(Long id) {
		return accountDAO.getAccount(id);
	}

	@Transactional(readOnly = true)
	public boolean emailExists(String email) {
		Account a = accountDAO.findBy("email", email, false);
		if (a != null) {
			return true;
		}
		return false;
	}

	@Transactional(readOnly = true)
	public boolean nameExists(String username) {
		Account a = accountDAO.findBy("name", username, false);
		if (a != null) {
			return true;
		}
		return false;
	}

	@Transactional(readOnly = true)
	public boolean validationCodeExists(String validationCode) {
		Account a = accountDAO.findBy("validationCode", validationCode, false);
		if (a != null) {
			return true;
		}
		return false;
	}

	@Transactional(propagation = Propagation.REQUIRED)
	public Account registerAccount(RegisterCommand rc) {
		Account account = new Account();
		account.setEmail(rc.getEmail());
		account.setName(rc.getName());
		String salt = RandomStringUtils.randomAlphanumeric(10);
		account.setSalt(salt);
		String encryptedPassword = passwordEncoder.encodePassword(rc
				.getPassword(), account.getSalt());
		account.setPassword(encryptedPassword);
		account = accountDAO.createAccount(account);
		Privilege privilege = privilegeDAO.getPrivilege("ROLE_USER");
		account = this.grantPrivileges(privilege, account);
		return account;
	}

	@Transactional
	public Account grantPrivileges(Privilege privilege, Account account) {
		Set<Privilege> existingPrivs = account.getPrivileges();
		Set<Privilege> privs = getPrivilegeChildren(privilege);
		existingPrivs.addAll(privs);
		account.setPrivileges(existingPrivs);
		account = accountDAO.updateAccount(account);
		return account;
	}

	private Set<Privilege> getPrivilegeChildren(Privilege privilege) {
		Set<Privilege> privs = new LinkedHashSet<Privilege>();
		log.debug("Looking for children of " + privilege);
		privs.add(privilege);
		Set<Privilege> children = privilege.getChildren();
		if (!children.isEmpty()) {
			for (Privilege p : children) {
				log.debug("Adding " + privilege);
				privs.addAll(getPrivilegeChildren(p));
			}
		}
		return privs;
	}

	@Transactional(propagation = Propagation.REQUIRED)
	public void updateLastLogin(Long accountId) {
		Account account = accountDAO.getAccount(accountId);
		account.setLastLogin(new Date());
		accountDAO.updateAccount(account);
	}

	@Transactional(propagation = Propagation.REQUIRED)
	public Account updateDetails(Long id, AccountDetailsCommand adc) {
		Account account = this.getAccount(id);
		Map<String, AccountProperty> props = account.getAccountProperties();
		addOrUpdateProperty(account, props, "birthDate", adc.getBirthDate());
		addOrUpdateProperty(account, props, "gender", adc.getGender());
		addOrUpdateProperty(account, props, "location", adc.getLocation());
		addOrUpdateProperty(account, props, "profile", adc.getProfile());
		addOrUpdateProperty(account, props, "url", adc.getUrl());
		addOrUpdateProperty(account, props, "aim", adc.getAim());
		addOrUpdateProperty(account, props, "google", adc.getGoogle());
		addOrUpdateProperty(account, props, "icq", adc.getIcq());
		addOrUpdateProperty(account, props, "msn", adc.getMsn());
		addOrUpdateProperty(account, props, "ps3", adc.getPs3());
		addOrUpdateProperty(account, props, "steam", adc.getSteam());
		addOrUpdateProperty(account, props, "twitter", adc.getTwitter());
		addOrUpdateProperty(account, props, "wii", adc.getWii());
		addOrUpdateProperty(account, props, "xbox", adc.getXbox());
		addOrUpdateProperty(account, props, "xfire", adc.getXfire());
		addOrUpdateProperty(account, props, "yahoo", adc.getYahoo());
		addOrUpdateProperty(account, props, "age", adc.getAge());
		return account;
	}

	@Transactional(propagation = Propagation.REQUIRED)
	public Account updateSettings(Long id, AccountSettingsCommand asc) {
		Account account = this.getAccount(id);
		Map<String, AccountProperty> props = account.getAccountProperties();
		addOrUpdateProperty(account, props, "threadsPerPage", asc
				.getThreadsPerPage());
		addOrUpdateProperty(account, props, "emailAllow", asc.getEmailAllow());
		addOrUpdateProperty(account, props, "emailHide", asc.getEmailHide());
		addOrUpdateProperty(account, props, "genderHide", asc.getGenderHide());
		addOrUpdateProperty(account, props, "msgAllow", asc.getMsgAllow());
		addOrUpdateProperty(account, props, "titleDisplay", asc
				.getTitleDisplay());
		return account;
	}

	@Transactional(propagation = Propagation.REQUIRED)
	protected void addOrUpdateProperty(Account account,
			Map<String, AccountProperty> props, String key, Object value) {
		AccountProperty prop = null;
		if (props.containsKey(key)) {
			prop = props.get(key);
			prop.setValue(value);
			props.put(key, prop);
			return;
		} else if (value != null) {
			// Figure out the object type and create the correct AccountProperty
			// child

			if (value.getClass().equals(Boolean.class)) {
				prop = new AccountBooleanProperty();
			} else if (value.getClass().equals(Date.class)) {
				prop = new AccountDateProperty();
			} else if (value.getClass().equals(Double.class)) {
				prop = new AccountDoubleProperty();
			} else if (value.getClass().equals(Long.class)) {
				prop = new AccountLongProperty();
			} else if (value.getClass().equals(String.class)) {
				if (StringUtils.isNotBlank((String) value)) {
					prop = new AccountStringProperty();
				}
			}
			prop.setAccount(account);
			prop.setName(key);
			prop.setValue(value);
			props.put(key, prop);
		}

	}

	@Transactional(propagation = Propagation.REQUIRED)
	public void updatePassword(Long id, String newPassword) {
		Account account = this.getAccount(id);
		String salt = RandomStringUtils.randomAlphanumeric(10);
		account.setSalt(salt);
		String password = passwordEncoder.encodePassword(newPassword, salt);
		account.setPassword(password);
		account.setValidationCode(null);
		accountDAO.updateAccount(account);
	}

	@Transactional(propagation = Propagation.REQUIRED)
	public void updateEmail(Long id, String newEmail) {
		Account account = this.getAccount(id);
		account.setEmail(newEmail);
		accountDAO.updateAccount(account);
	}

	@Transactional(propagation = Propagation.REQUIRED)
	public void doValidationEmail(String email, String ipAddress)
			throws Exception {
		Account account = accountDAO.findBy("email", email, false);
		String validationCode = RandomStringUtils.randomAlphanumeric(32);
		account.setValidationCode(validationCode);
		accountDAO.updateAccount(account);
		emailService.sendValidationCode(account.getEmail(), validationCode,
				ipAddress);

	}

	@Transactional(propagation = Propagation.REQUIRED)
	public void doPasswordResetEmail(String validationCode, String ipAddress)
			throws Exception {
		Account account = accountDAO.findBy("validationCode", validationCode,
				false);
		String password = RandomStringUtils.randomAlphanumeric(8);
		updatePassword(account.getId(), password);
		emailService.sendNewPassword(account.getEmail(), password, ipAddress);
	}

	public List<Privilege> getPrivileges() {
		return privilegeDAO.getPrivileges();
	}

	public Account getAccount(String user) {
		Account account = accountDAO.findBy("name", user);
		return account;
	}

}
